package cd.cdyb.sms.service.impl;

import cd.cdyb.sms.dao.CompanyDao;
import cd.cdyb.sms.dao.InventoryFlowDao;
import cd.cdyb.sms.domain.Company;
import cd.cdyb.sms.domain.InventoryFlow;
import cd.cdyb.sms.dto.expand.ExCompanyDto;
import cd.cdyb.sms.service.CompanyService;
import cd.cdyb.sms.service.EmailService;
import cd.cdyb.sms.service.InventoryService;
import cd.cdyb.sms.service.RedisService;
import cd.cdyb.sms.utils.CacheUtils;
import cd.cdyb.sms.utils.JacksonUtilPlus;
import cd.cdyb.sms.utils.RedissonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateFormatUtils;
import org.redisson.api.RLock;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Service("inventoryService")
@Slf4j
public class InventoryServiceImpl implements InventoryService {

    @Resource
    private RedisService redisService;

    @Resource
    private CompanyService companyService;

    @Resource
    private CompanyDao companyDao;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Value("${mq.routeKey}")
    private String routeKey;

    @Resource
    private EmailService emailService;

    @Resource
    private RedissonUtil redissonUtil;

    @Resource
    private InventoryFlowDao inventoryFlowDao;

    @Override
    public Boolean increaseInventory(String companykey, Integer count, Integer type) {

        String typeStr = type.toString();


        Long newStock = 0L;
        RLock lock = redissonUtil.getLock("UPDATE_COMPANY");
        try {
            // 尝试加锁，最多等待5秒，上锁以后10秒自动解锁
            boolean isOk = lock.tryLock(8, 10, TimeUnit.SECONDS);
            if (!isOk) {
                // 获取分布式锁失败，可以进行抛出获取锁失败的提示等，根据业务来
                log.info("获取分布式锁失败");
                return false;
            }
            Object o = redisService.vGet(CacheUtils.CacheName.COMPANY_SMS_INVENTORY, companykey + ":" + typeStr);

            if (o == null) {
                ExCompanyDto exCompanyDto = companyService.getCompanyByKey(companykey);
                //type 5-营销 6-行业
                if (5 == type) {
                    redisService.vPut(CacheUtils.CacheName.COMPANY_SMS_INVENTORY, companykey + ":" + typeStr, exCompanyDto.getMarketingCount());
                } else {
                    redisService.vPut(CacheUtils.CacheName.COMPANY_SMS_INVENTORY, companykey + ":" + typeStr, exCompanyDto.getIndustryCount());
                }
            }
            newStock = redisService.vIncrement(CacheUtils.CacheName.COMPANY_SMS_INVENTORY, companykey + ":" + typeStr, count.longValue());
            log.info("[{}]增加库存[{}], 剩余[{}]", companykey, count, newStock);

        } catch (InterruptedException e) {
            // 如果10s没有执行完，锁会自动释放，这里抛出异常
            Thread.currentThread().interrupt();
        } finally {
            // 如果10s没有执行完，锁会自动释放，这里抛出异常，操作失败，需要对数据回滚
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

        InventoryFlow inventoryFlow = new InventoryFlow();
        inventoryFlow.setAfterValue(newStock.intValue());
        inventoryFlow.setCompanyKey(companykey);
        inventoryFlow.setInventoryValue(count);
        inventoryFlow.setType(type);
        inventoryFlow.setDirection(1);
        rabbitTemplate.convertAndSend(routeKey, JacksonUtilPlus.toJSon(inventoryFlow));
//        log.info("[{}]充值加库存, 剩余[{}]", companykey, newStock);
        return true;

    }

    @Override
    public Boolean reduceInventory(String companykey, Integer count, Integer type) {

        String typeStr = type.toString();
        //不可在这里直接操作数据库减库存，否则导致数据不安全
        //因为此时可能有其他线程已经将redis的key修改了
        //redis 减少库存，然后才能操作数据库
        Long newStock = 0L;
        RLock lock = redissonUtil.getLock("UPDATE_COMPANY");
        ExCompanyDto exCompanyDto = companyService.getCompanyByKey(companykey);

        try {
            // 尝试加锁，最多等待5秒，上锁以后10秒自动解锁
            boolean isOk = lock.tryLock(8, 10, TimeUnit.SECONDS);
            if (!isOk) {
                // 获取分布式锁失败，可以进行抛出获取锁失败的提示等，根据业务来
                log.info("获取分布式锁失败");
                return false;
            }


            Object o = redisService.vGet(CacheUtils.CacheName.COMPANY_SMS_INVENTORY, companykey + ":" + typeStr);
            if (o == null) {
                exCompanyDto = companyService.getCompanyByKey(companykey);
                //type 5-营销 6-行业
                if (5 == type) {
                    o = exCompanyDto.getMarketingCount();
                    redisService.vPut(CacheUtils.CacheName.COMPANY_SMS_INVENTORY, companykey + ":" + typeStr, exCompanyDto.getMarketingCount());
                } else {
                    o = exCompanyDto.getIndustryCount();
                    redisService.vPut(CacheUtils.CacheName.COMPANY_SMS_INVENTORY, companykey + ":" + typeStr, exCompanyDto.getIndustryCount());
                }

            }
            //先检查 库存是否充足
            Integer stock = (Integer) o;
            if (stock < count) {
//            log.info("[{}]公司KEY, 库存不足", companykey);
                return false;
            }
            newStock = redisService.vDecrement(CacheUtils.CacheName.COMPANY_SMS_INVENTORY, companykey + ":" + typeStr, count.longValue());
            if (newStock % 1000 == 0){
                log.info("[{}]减少库存[{}], 剩余[{}]", companykey, count, newStock);
            }
        } catch (InterruptedException e) {
            // 如果10s没有执行完，锁会自动释放，这里抛出异常
            Thread.currentThread().interrupt();
        } finally {
            // 如果10s没有执行完，锁会自动释放，这里抛出异常，操作失败，需要对数据回滚
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }


        Integer industryPrompt = exCompanyDto.getIndustryPrompt();

        //如果库存小于提示数量
        if (newStock < industryPrompt) {
            String days = DateFormatUtils.format(new Date(), "yyyyyMMdd");
            Object r = redisService.vGet(CacheUtils.CacheName.DAY_INFO, days + ":" + companykey);
            if (r == null){
                //发送短信
                log.info("[{}]公司KEY, 发送邮件", companykey);
                emailService.sendEmail(exCompanyDto.getEmail(), "短信库存不足", "尊敬的客户，截止目前您的短信余量已不足" + newStock + "条，请及时充值以免影响正常业务！");

                //保证今天只缓存一次
                redisService.vPut(CacheUtils.CacheName.DAY_INFO, days + ":" + companykey, 1);
            }
        }
        //库存充足
        if (newStock >= 0) {
            //真正扣库存操作 可用MQ 进行 redis 和 mysql 的数据同步，减少响应时间

            InventoryFlow inventoryFlow = new InventoryFlow();
            inventoryFlow.setAfterValue(newStock.intValue());
            inventoryFlow.setCompanyKey(companykey);
            inventoryFlow.setInventoryValue(count);
            inventoryFlow.setType(type);
            inventoryFlow.setDirection(0);
            rabbitTemplate.convertAndSend(routeKey, JacksonUtilPlus.toJSon(inventoryFlow));
            return true;
        }
//        else {
//            log.info("触发库存不足的时候自动补偿");
//            //库存不足，需要增加刚刚减去的库存
//            redisService.vIncrement(CacheUtils.CacheName.COMPANY_SMS_INVENTORY, companykey + ":" + typeStr, count.longValue());
//            return false;
//        }
//        log.info("[{}]发送短信减库存, 剩余[{}]", companykey, newStock);

        return false;

    }



    public void updateInventory(String companyKey) {

        Company company = companyDao.getCompanyByCompanyKey(companyKey);

        LocalDateTime endLocalDateTime = LocalDateTime.now();
//                LocalDateTime endLocalDateTime = now.minusMinutes(1);
        LocalDateTime startLocalDateTime = endLocalDateTime.minusMinutes(10);
        //暂时只统计行业短信
        Integer incInventoryFlowCount = inventoryFlowDao.getInventoryFlowCountByCompanyId(companyKey, 1, 6, startLocalDateTime, endLocalDateTime);
        Integer reduceInventoryFlowCount = inventoryFlowDao.getInventoryFlowCountByCompanyId(companyKey, 0, 6, startLocalDateTime, endLocalDateTime);
        int count = incInventoryFlowCount - reduceInventoryFlowCount;
        Integer industryCount = company.getIndustryCount();
        industryCount += count;
        company.setIndustryCount(industryCount);
        companyDao.updateById(company);


    }
}
